// Created by denglibin on 19-5-8.
// 矩阵 变换
//
#include "settings.h"
#include "shader.h"
#include "buffer_obj.h"
#include "init.h"
#include "call_back.h"
#include "texture.h"
#include <GLFW/glfw3.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <unistd.h>
#include "Camera.h"
//坐标系统,3d

/**
5个不同的坐标系统：

局部空间(Local Space，或者称为物体空间(Object Space))
 --(模型矩阵)->
世界空间(World Space)
 --(观察矩阵)-->
观察空间(View Space，或者称为视觉空间(Eye Space))
 --(投影矩阵(透视投影矩阵))-->
裁剪空间(Clip Space)
屏幕空间(Screen Space)

 */

static Camera camera; //创建摄像机对象
static float fov = 45.0f;//视野范围 度
static float deltaTime = 0.0f; // 当前帧与上一帧的时间差
static float lastFrame = 0.0f; // 上一帧的时间

//10个立方体的位移向量(不同的位置)
static  glm::vec3 cubePositions[] = {
        glm::vec3( 0.0f,  0.0f,  0.0f),
        glm::vec3( 2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.0f, -12.3f),
        glm::vec3( 2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f,  3.0f, -7.5f),
        glm::vec3( 1.3f, -2.0f, -2.5f),
        glm::vec3( 1.5f,  2.0f, -2.5f),
        glm::vec3( 1.5f,  0.2f, -1.5f),
        glm::vec3(-1.3f,  1.0f, -1.5f)
};

/**
 * 键盘输入事件
 * @param window
 */
static void processInput4(GLFWwindow *window){
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) //esc键 关闭窗口
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS){
        camera.moveFront(deltaTime); //摄像机前进
    }
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
       camera.moveBack(deltaTime); //摄像机后退

    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS){
        camera.moveLeft(deltaTime); //摄像机左移
    }
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.moveRight(deltaTime); //摄像机右移
    if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
        camera.moveUp(deltaTime); //摄像机上移
    if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
        camera.moveDown(deltaTime); //摄像机下移
}

/**
 * 鼠标输入事件
 * @param window
 * @param xpos
 * @param ypos
 */
static void mouse_callback(GLFWwindow* window, double xpos, double ypos){
    camera.updateFront(xpos, ypos);
}

/**
 * 滚轮改变视野大小
 * @param window
 * @param xoffset
 * @param yoffset
 */
static void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    if(fov >= 1.0f && fov <= 45.0f)
        fov -= yoffset;
    if(fov <= 1.0f)
        fov = 1.0f;
    if(fov >= 45.0f)
        fov = 45.0f;
}
/**
 * 创建缓冲对象
 * @param vertices
 * @param indices
 */
static void createBufferObj(unsigned  int * VBO,float vertices[], int vSize){
    *VBO = createVBO(vertices, vSize); //顶点缓冲对象
}

/**
 * 设置着色器程序变量的值
 * @param shaderProgramId 着色器程序id
 * @param i 第几个立方体
 */
static void setUniformValue(int shaderProgramId, int i){
    //float timeValue = glfwGetTime(); //获取运行的秒数

    //设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元
    setInt(shaderProgramId, "texture1", 0);
    setInt(shaderProgramId, "texture2", 1);

    //1.模型矩阵 应用到所有物体的顶点上，以变换它们到全局的世界空间
    glm::mat4 model = glm::mat4(1.0f); //单位矩阵(变换矩阵)
    //绕着x轴旋转，
   // model = glm::rotate(model,  (float)glfwGetTime() * glm::radians(-60.0f), glm::vec3(1.0f, 0.0f, 0.0f));
   //移动
    model = glm::translate(model, cubePositions[i]);
    float angle = 10.0f * i + 20 ;
    //旋转 radians: 将角度转为弧度
    model = glm::rotate(model, (float)glfwGetTime()* glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
  //2.观察矩阵 在场景里面稍微往后移动(向屏幕外面)，以使得物体变成可见的（当在世界空间时，我们位于原点(0,0,0)）
//    glm::mat4 view = glm::mat4(1.0f);
//    //将观察矩阵在各个方向上进行位移(改变x,y的大小)   std::cout << pitch<<"," << yaw<<std::endl;，来看看场景是如何改变的。注意把观察矩阵当成摄像机对象。(x增大:场景右移 相当于摄像机左移动 y增大:摄像机下移动)
//    view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));//右手坐标系(z轴指向屏幕外面为正向,指向屏幕里面为负方),越往屏幕里面越小

//使用lookAt函数创建观察矩阵
/**2.1 摄像机围绕目标旋转
    float radius = 25.0f; //半径
    float camX = sin(glfwGetTime()) * radius; //x坐标
    float camZ = cos(glfwGetTime()) * radius; //z坐标
    //使用lookAt来得到一个观察矩阵(相当于摄像机),围绕y轴旋转  参数:位置、目标(原点)和上向量(y轴)
    glm::mat4 view =  glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));
*/

    //2.2 通过摄像机来获取观察矩阵
    glm::mat4 view = camera.getViewMatrix();

    //3.透视投影矩阵
    glm::mat4 projection = glm::mat4(1.0f);
    /**
     * 它的第一个参数定义了fov的值，它表示的是视野(Field of View)，并且设置了观察空间的大小。如果想要一个真实的观察效果，它的值通常设置为45.0f，但想要一个末日风格的结果你可以将其设置一个更大的值。
     * 第二个参数设置了宽高比，由视口的宽除以高所得。
     * 第三和第四个参数设置了平截头体的近和远平面。我们通常设置近距离为0.1f，而远距离设为100.0f
     */
    projection = glm::perspective(glm::radians(fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

    //设置uniform变量的值
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "model"), 1, GL_FALSE, glm::value_ptr(model));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "view"), 1, GL_FALSE, glm::value_ptr(view));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "projection"), 1, GL_FALSE, glm::value_ptr(projection));

}

static void show(){
    //顶点数据
    // 要想渲染一个立方体，我们一共需要36个顶点（6个面 x 每个面有2个三角形组成 x 每个三角形有3个顶点）
    float vertices[] = {
            //   ---- 位置 ----   - 纹理坐标 -
            //第一个面第一个三角形  纹理三角形
            -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
            0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
            0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
            //第一个面第二个三角形  纹理三角形
            0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
            -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

            //第二个面
            -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
            0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
            0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
            0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
            -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
            -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

            -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
            -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
            -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
            -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

            0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
            0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
            0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
            0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
            0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
            0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

            -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
            0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
            0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
            0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
            -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

            -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
            0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
            0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
            0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
            -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
            -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };

    //初始化
    init();
    //创建窗口对象
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, TITLE, NULL, NULL);
    if (window == NULL)
    {
        printf("Failed to create GLFW window");
        glfwTerminate();
        exit(-1);
    }
    //通知GLFW将窗口的上下文设置为当前线程的主上下文
    glfwMakeContextCurrent(window);
    //对窗口注册一个回调函数,每当窗口改变大小，GLFW会调用这个函数并填充相应的参数供你处理
    glfwSetFramebufferSizeCallback(window, frameSizeChangeCallBack);
    glfwSetCursorPosCallback(window, mouse_callback); //鼠标捕捉
    //隐藏光标，并捕捉(Capture)它 在调用这个函数之后，无论我们怎么去移动鼠标，光标都不会显示了，它也不会离开窗口
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    glfwSetScrollCallback(window, scroll_callback); //滚轮事件

    //初始化GLAD用来管理OpenGL的函数指针
    //GLFW给我们的是glfwGetProcAdress，它根据我们编译的系统定义了正确的函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        printf("Failed to initialize GLAD");
        exit(-1);
    }
    //在开始渲染之前必须告诉OpenGL渲染窗口(Viewport)的尺寸大小，这样OpenGL才能知道怎样根据窗口大小显示数据和坐标。
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);

    //创建着色器程序
    ShaderProgram shaderProgram = {0, "shader_code/vertex04.vs", "shader_code/fragment04.fs"};
    createShaderProgram(&shaderProgram);
    //创建顶点数组对象
    unsigned int VAO; //顶点属性数组对象id
    glGenVertexArrays(1, &VAO); //创建顶点数组对象
    //绑定VAO
    //顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定，任何随后的顶点属性调用都会储存在这个VAO中
    glBindVertexArray(VAO);
    //创建缓冲顶点缓冲对象
    unsigned  int VBO;
    createBufferObj(&VBO, vertices, sizeof(vertices));
    //创建纹理对象
    unsigned  int texture1 = createTextureObj("resources/container.jpg", 0, GL_RGB);
    unsigned  int texture2 = createTextureObj("resources/awesomeface.png", 1, GL_RGBA);

    /**
    * 设置顶点属性指针(位置):
       第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗？它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中，所以这里我们传入0。
       第二个参数指定顶点属性的大小。顶点属性是一个vec3，它由3个值组成，所以大小是3。
       第三个参数指定数据的类型，这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
       第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE，所有数据都会被映射到0（对于有符号型signed数据是-1）到1之间。我们把它设置为GL_FALSE。
       第五个参数叫做步长(Stride)，它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3(6)个float之后，我们把步长设置为3(6) * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的（在两个顶点属性之间没有空隙）我们也可以设置为0来让OpenGL决定具体步长是多少（只有当数值是紧密排列时才可用）。一旦我们有更多的顶点属性，我们就必须更小心地定义每个顶点属性之间的间隔，我们在后面会看到更多的例子（译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节）。
       最后一个参数的类型是void*，所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头，所以这里是0。
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    //启用顶点属性
    glEnableVertexAttribArray(0);
    /**
     * 设置顶点属性指针(纹理):
     */
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(2);


    // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    glBindVertexArray(0);

    /**
     * 设置图形模式:
     */
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //线框模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); //填充模式
    //启用深度测试
    glEnable(GL_DEPTH_TEST);
    glUseProgram(shaderProgram.id);
    //设置着色器代码中变量的值
    //渲染循环 使得GLFW在退出之前一直保持运行
    while(!glfwWindowShouldClose(window))
    {
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;
        // 输入控制
        processInput4(window);
        //渲染指令
        //当调用glClear函数，清除颜色缓冲之后，整个颜色缓冲都会被填充为glClearColor里所设置的颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        //glClear(GL_COLOR_BUFFER_BIT);
        //清除深度缓冲（否则前一帧的深度信息仍然保存在缓冲中）
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        //bind textures on corresponding texture units
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);

        glUseProgram(shaderProgram.id);
        glBindVertexArray(VAO);
        //绘制10个立方体
        for(int i = 0; i < 10; i++){
            //设置着色器代码中变量的值
            setUniformValue(shaderProgram.id, i);
            //立方体36个点
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        //检查并调用事件，交换缓冲
        glfwSwapBuffers(window); //交换颜色缓冲
        //检查触发事件 函数检查是否有触发事件，比如键盘、鼠标等信号输入，然后更新窗口状态，调用相应的回调函数（可通过回调方法手动设置）。
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    //释放/删除之前的分配的所有资源
    glfwTerminate();
}

static  void testNormalize(){
    glm::vec3 v(0.2,0.1,0.6);
    v = glm::normalize(v);
    std::cout << v.x << "," <<v.y<<","<<v.z<<std::endl;
}
int main4(){
    //testGlm();
    //testNormalize();
    show();
    return 0;
}
